在 C 語言程式設計中,與使用者或檔案進行互動的核心是輸入 (Input) 與輸出 (Output),統稱為 I/O。C 語言透過其標準函式庫 (Standard Library) 提供了一系列豐富的 I/O 函式。這些函式大多被宣告在 <stdio.h> 這個標頭檔 (Header File) 中,因此在使用它們之前,我們幾乎總會在程式的開頭寫上 #include <stdio.h>。
本章將探討幾組常用的 I/O 函式,從格式化 I/O、字串 I/O 到字元 I/O,並分析它們的原理、用法與差異。
scanf 與 printf(格式化 I/O)這是 C 語言中最常用的一對 I/O 函式,它們能夠根據指定的格式來讀取和寫入多種資料型態。
printf(格式字串, ...): 將資料(變數、常數等)依照指定的格式轉換成文字,並輸出到標準輸出(通常是螢幕)。scanf(格式字串, ...): 從標準輸入(通常是鍵盤)讀取文字,並依照指定的格式將其解析、轉換後存入指定的變數位址中。#include <stdio.h>
int main() {
// === 範例一:讀取與輸出多個整數 ===
printf("請輸入三個整數 (以空格分隔): ");
int input1, input2, input3;
scanf("%d %d %d", &input1, &input2, &input3); // 注意變數名前的 '&'
printf("您輸入的數字是: %d %d %d\n", input1, input2, input3);
// === 範例二:讀取字串 ===
printf("\n請輸入一個單字 (不含空格): ");
char str1[20];
scanf("%s", str1); // 讀取字串時,陣列名稱本身就是位址,不需加 '&'
printf("您輸入的單字是: %s\n", str1);
return 0;
}
scanf 原理與常見行為scanf 會從標準輸入(鍵盤)讀取資料,依照格式控制符(如 %d, %s)將輸入的資料存入對應變數。scanf 在讀取時,預設會以空白字元 (Whitespace),包含空格、Tab、換行符等,作為資料的分隔符。當使用 %s 讀取字串時,它會在遇到第一個空白字元時停止讀取。scanf 會讀取並跳過輸入緩衝區中的換行符。& 運算子:scanf 的目的是要「改變」變數的值,所以必須傳遞變數的記憶體位址給它,因此在變數名前要加上取址運算子 &。唯一的例外是字串陣列,因為陣列名稱本身就代表了其起始位址。printf 原理與常見行為printf 負責格式化輸出資料到螢幕。\n 才會換行。sscanf 與 sprintf(字串格式化 I/O)這對函式的功能與 scanf/printf 非常相似,但它們的操作對象不是標準輸入/輸出流,而是記憶體中的字串。
sprintf(目標字串緩衝區, 格式字串, ...): 將格式化的資料「印」到一個字串中,而不是螢幕。sscanf(來源字串, 格式字串, ...): 從一個字串中「讀取」格式化的資料。#include <stdio.h>
int main() {
char str[10];
int dec;
float pi;
// 使用 sscanf 從字串中解析資料
char input_source[] = "abc 50 3.145";
sscanf(input_source, "%s %d %f", str, &dec, &pi);
printf("從字串 \"%s\" 解析出的資料:\n", input_source);
printf("字串: %s, 整數: %d, 浮點數: %.3f\n\n", str, dec, pi);
// 使用 sprintf 將資料格式化並存入字串
char output_buffer[80];
sprintf(output_buffer, "圓周率的值是: %.3f", pi);
printf("將資料格式化後存入字串,結果為:\n");
printf("%s\n", output_buffer);
return 0;
}
sscanf 原理與常見行為sscanf 是「string scan formatted」,和 scanf 類似,但來源不是鍵盤輸入,而是「一段字串」。scanf 完全一樣。會依空白字元(空格、tab、換行)分隔不同資料欄位。sscanf(str, "%d %f", &a, &b); 可從 str 字串中解析兩個欄位。\n 或結尾。sprintf 原理與常見行為sprintf 是「string print formatted」,會將格式化後的內容寫入指定的字串陣列,而不是螢幕。printf 幾乎一樣,差別在於「輸出目標」是陣列不是螢幕。sprintf 時要特別小心,必須確保目標字串緩衝區 (output_buffer) 足夠大,能夠容納所有格式化後的內容,否則會造成緩衝區溢位 (Buffer Overflow),這是一個嚴重的安全漏洞。更安全的替代方案是 snprintf,它允許指定最大寫入長度。\n。gets 與 puts(行導向 I/O (注意:gets 已被棄用))這組函式以「行」為單位來處理字串。
puts(字串): 輸出一行字串到標準輸出,並自動在結尾加上一個換行符 \n。gets(字串緩衝區): 從標準輸入讀取一行字串,直到遇到換行符為止。#include <stdio.h>
int main() {
// puts 的使用
char str_s[] = "I love coding.";
puts("--- puts 範例 ---");
puts(str_s); // 會輸出 I love coding. 並換行
// 比較 puts 與 printf
char str_s2[] = "I don't love coding.";
puts("\n--- printf 範例 (無 \\n) ---");
printf("%s", str_s2); // 只輸出 I don't love coding. 不會自動換行
printf(" (printf 輸出的結尾)\n");
/* * gets 的範例在此省略,因為它極度不安全。
* char input_str[10];
* gets(input_str); // 如果使用者輸入超過9個字元,就會發生緩衝區溢位!
*/
return 0;
}
gets 的危險性:gets 函式在讀取輸入時,完全不檢查目標緩衝區的大小。如果使用者輸入的字串長度超過了陣列的容量,多出的字元會覆寫到相鄰的記憶體,引發不可預期的行為或安全漏洞。gets 函式已在 C11 標準中被正式移除。絕對不要在任何新的程式碼中使用 gets。請永遠使用 fgets 作為替代。puts 的便利性:puts 會自動加上換行符,對於單純輸出整行字串的場景,比 printf 更簡潔。fgets 與 fputs(安全的檔案/流導向 I/O)這組函式是 gets 和 puts 的安全且更通用的版本,它們可以對任何檔案流 (File Stream) 進行操作,包括標準輸入 (stdin) 和標準輸出 (stdout)。
fgets(緩衝區, 大小, 檔案流): 從指定的流讀取一行字串。fputs(字串, 檔案流): 將一個字串寫入指定的流。#include <stdio.h>
int main() {
// fgets 從標準輸入讀取一行
char str_fgets[20];
printf("請輸入一行文字 (最多19個字元): ");
fgets(str_fgets, sizeof(str_fgets), stdin); // sizeof(str_fgets) 確保不會溢位
printf("fgets 的讀取結果: %s", str_fgets);
// fputs 寫入到標準輸出
char str_puts[] = "I love you. ";
char str_puts1[] = "And you?";
fputs(str_puts, stdout);
fputs(str_puts1, stdout);
printf("\n");
return 0;
}
fgets 的安全性:fgets 最重要的特性是它的第二個參數(大小)。它告訴函式緩衝區最多能容納多少個字元。fgets 讀取時絕不會超過這個大小減一(保留一個位置給結尾的 \0),從而徹底避免了緩衝區溢位。fgets 會讀取並保留輸入中的換行符 \n(如果緩衝區空間足夠的話)。這就是為什麼上面範例的 printf 輸出後面通常會多一個換行。fputs 的行為:與 puts 不同,fputs 不會自動在結尾添加換行符。如範例所示,兩次 fputs 的輸出會連在一起。stdin 和 stdout 是在 <stdio.h> 中定義的兩個預設檔案流,分別代表標準輸入和標準輸出。fgets 和 fputs 的設計使其可以輕易地用於檔案讀寫,只需將 stdin/stdout 替換成一個用 fopen() 開啟的檔案指標即可。getchar/putchar 與 getc/putc(字元導向 I/O)這幾組函式是 C 語言中最基礎的 I/O,它們一次只處理一個字元。getchar(): 等同於 getc(stdin),從標準輸入讀取一個字元。putchar(字元): 等同於 putc(字元, stdout),將一個字元寫入標準輸出。
#include <stdio.h>
int main() {
printf("請輸入一個字元: ");
char c = getc(stdin);
printf("putc 的輸出: ");
putc(c, stdout);
// 清除輸入緩衝區中的換行符
while (getchar() != '\n');
printf("\n\n請再輸入一個字元: ");
char b = getchar();
printf("putchar 的輸出: ");
putchar(b);
printf("\n");
return 0;
}
getchar() 讀取到的是字元 '1'(其 ASCII 碼為 49),而不是整數值 1。getchar/putchar 是 getc/putc 針對標準輸入輸出的特化版本(巨集定義),使用上更簡潔。而 getc/putc 則更通用,可以操作任何檔案流。getchar 從中讀取第一個字元後,其餘的字元(包括 \n)仍會留在緩衝區中,可能會影響下一次的輸入讀取。範例中 while (getchar() != '\n'); 是一種常見的清空緩衝區的方法。| 指令 | 輸入/輸出 | 自動換行 | 空格行為 | 何時結束 | 備註 |
|---|---|---|---|---|---|
| puts | 輸出 | ✅ | 照常輸出 | \\0 |
每次自動加換行 |
| printf | 輸出 | ❌ | 照常輸出 | 依格式字串 | 需手動加 \\n |
| fgets | 輸入 | - | ✅ | 長度或換行或結尾 | 換行符也會存進陣列 |
| fputs | 輸出 | ❌ | 照常輸出 | \\0 |
不自動加換行 |
| getc | 輸入 | - | ✅ | 每次讀一字元 | getc(stdin) = getchar() |
| putc | 輸出 | ❌ | ✅ | 每次寫一字元 | putc(c, stdout) = putchar(c) |
| getchar | 輸入 | - | ✅ | 每次讀一字元 | 只從 stdin |
| putchar | 輸出 | ❌ | ✅ | 每次寫一字元 | 只到 stdout |